package com.linkface.liveness.ui;

import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Sensor;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.linkface.liveness.LFLivenessSDK;
import com.linkface.liveness.util.Constants;
import com.linkface.liveness.util.LivenessUtils;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Copyright (c) 2017-2018 LINKFACE Corporation. All rights reserved.
 * <p>
 * 实时人脸检测接口调用示例
 **/
public class FaceOverlapFragment extends CameraOverlapFragment implements PreviewCallback {
    private static final String TAG = "FaceOverlapFragment";
    //TODO will changed to use the web return value
    private static final int DETECT_WAIT_TIME = 1 * 1000;
    private static final boolean DEBUG_PREVIEW = false;

    private OnLivenessCallBack mListener;
    private boolean mIsKilled = false;
    public boolean mPaused = true;
    private boolean mNV21DataIsReady = false;
    private byte mNv21[];
    private LFLivenessSDK.LFLivenessMotion[] mMotionList;
    private boolean mLiveResult[];
    private int mCurrentMotion = 0;
    private LFLivenessSDK mDetector = null;
    private long mStartTime;
    private int mFrameCount = 0;
    private boolean mIsFirstPreviewFrame = true;
    private long mFirstFrameTime;
    private boolean mBeginShowWaitUIBoolean = true;
    private boolean mEndShowWaitUIBoolean = false;
    private boolean mIsDetectorStartSuccess = false;
    public boolean mIsCreateHandleSuccess = false;
    private ExecutorService mDetectorExecutor;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = super.onCreateView(inflater, container, savedInstanceState);

        mMotionList = LivenessUtils.getMctionOrder(getActivity().getIntent()
                .getStringExtra(LivenessActivity.EXTRA_MOTION_SEQUENCE));
        if (mMotionList.length > 0) {
            mLiveResult = new boolean[mMotionList.length];
            for (int i = 0; i < mMotionList.length; i++) {
                mLiveResult[i] = false;
            }
        }

        initStateAndPreviewCallBack();
        mIsKilled = false;
        return view;
    }

    private int getLivenessConfig() {
        Bundle bundle = getActivity().getIntent().getExtras();
        LFLivenessSDK.LFLivenessOutputType outputType = LFLivenessSDK.LFLivenessOutputType.getOutputTypeByValue(bundle.getString(LivenessActivity.OUTTYPE));
        LFLivenessSDK.LFLivenessComplexity complexity = LFLivenessSDK.LFLivenessComplexity.getComplexityByValue(bundle.getString(LivenessActivity.COMPLEXITY));

        return outputType.getValue() | complexity.getValue();
    }

    @Override
    public void onResume() {
        super.onResume();

        if (mDetectorExecutor == null) {
            mDetectorExecutor = Executors.newSingleThreadExecutor();
        }

        mDetectorExecutor.execute(new Runnable() {
            @Override
            public void run() {
                while (!mIsKilled) {
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (mPaused) {
                        if (mDetector != null) {
                            mDetector.end();
                        }
                        continue;
                    }
                    if (!mPaused && mEndShowWaitUIBoolean) {
                        synchronized (this) {
                            startLivenessIfNeed();
                        }
                        doDetect();
                        mNV21DataIsReady = false;
                    }
                }

                if (mDetector != null) {
                    releaseDetector();
                }
            }
        });
    }

    private void releaseDetector() {
        synchronized (this) {
            if (mDetector != null) {
                mDetector.end();
                mDetector.destroy();
                mDetector = null;
            }
        }
    }

    /**
     * 进行活体检测
     */
    private void doDetect() {
        LFLivenessSDK.LFStatus status = null;
        if (mDetector != null) {
            try {
                if (mCurrentMotion < mMotionList.length) {
                    if (mIsDetectorStartSuccess) {
                        synchronized (mNv21) {
                            status = mDetector.detect(mNv21, Constants.PREVIEW_WIDTH, Constants.PREVIEW_HEIGHT, mCameraInfo.orientation, mMotionList[mCurrentMotion]);
                        }
                    }
                }
            } catch (Exception e) {
                status.setDetectStatus(LFLivenessSDK.LFDetectStatus.INTERNAL_ERROR.getValue());
                e.printStackTrace();
            }
        }
        if (status == null) {
            return;
        }
        if (mCurrentMotion < mMotionList.length) {
            if (status.getDetectStatus() == LFLivenessSDK.LFDetectStatus.TRACKING_MISSED.getValue()) {
                finishDetect(Constants.LIVENESS_TRACKING_MISSED, mCurrentMotion);
            }
        }
        if (status.getDetectStatus() == LFLivenessSDK.LFDetectStatus.PASSED.getValue() && status.isPassed()) {
            if (mCurrentMotion < mMotionList.length) {
                mLiveResult[mCurrentMotion] = true;
                if (mLiveResult[mCurrentMotion]) {
                    mCurrentMotion++;
                    mDetector.detect(mNv21,
                            Constants.PREVIEW_WIDTH,
                            Constants.PREVIEW_HEIGHT,
                            mCameraInfo.orientation, LFLivenessSDK.LFLivenessMotion.NONE);
                    if (mCurrentMotion == mMotionList.length) {
                        finishDetect(Constants.LIVENESS_SUCCESS, mCurrentMotion);
                    } else {
                        restartDetect(true);
                    }
                }
            }
        }
        addPreviewCallbackBuffer();
    }

    private void finishDetect(int livenessSuccess, int mCurrentMotion) {
        stopLiveness();
        if (null != mListener) {
            mListener.onLivenessDetect(livenessSuccess,
                    mCurrentMotion, getLivenessResult(), getVideoResult(), getImageResult());
        }
        releaseDetector();
    }

    @Override
    public void onStop() {
        super.onStop();
        releaseDetector();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopDetectThread();
        if (mNv21 != null) {
            mNv21 = null;
        }
    }

    private byte[] getLivenessResult() {
        try {
            synchronized (this) {
                if (mDetector != null) {
                    mDetector.end();
                    return mDetector.getLivenessResult();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    private LFLivenessSDK.LFLivenessImageResult[] getImageResult() {
        try {
            synchronized (this) {
                if (mDetector != null) {
                    mDetector.end();
                    return mDetector.getImageResult();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private byte[] getVideoResult() {
        try {
            synchronized (this) {
                if (mDetector != null) {
                    mDetector.end();
                    return mDetector.getVideoResult();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
     * set the WrapperStaticInfo here.
     */
    public void setWrapperStaticInfo() {
        try {
            mDetector.setStaticInfo(LFLivenessSDK.LFWrapperStaticInfo.DEVICE.getValue(), android.os.Build.MODEL);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            mDetector.setStaticInfo(LFLivenessSDK.LFWrapperStaticInfo.OS.getValue(), "Android");
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            mDetector.setStaticInfo(LFLivenessSDK.LFWrapperStaticInfo.SDK_VERSION.getValue(),
                    LFLivenessSDK.getSDKVersion());
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            mDetector.setStaticInfo(LFLivenessSDK.LFWrapperStaticInfo.SYS_VERSION.getValue(),
                    android.os.Build.VERSION.RELEASE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            mDetector.setStaticInfo(LFLivenessSDK.LFWrapperStaticInfo.ROOT.getValue(), String.valueOf(LivenessUtils.isRootSystem()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            mDetector.setStaticInfo(LFLivenessSDK.LFWrapperStaticInfo.CUSTOMER.getValue(), getActivity().getApplicationContext().getPackageName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initStateAndPreviewCallBack() {
        mCurrentMotion = 0;
        mNv21 = new byte[Constants.PREVIEW_WIDTH * Constants.PREVIEW_HEIGHT * 3
                / 2];
        this.setPreviewCallback(this);
        addPreviewCallbackBuffer();
    }

    private void startLivenessIfNeed() {
        if (mDetector == null) {
            try {
                mDetector = LFLivenessSDK.getInstance(getActivity());
                int createResultCode = mDetector.createHandle();
                int errorCode = LivenessActivity.RESULT_INTERNAL_ERROR;
                switch (createResultCode) {
                    case LFLivenessSDK.LF_LIVENESS_INIT_SUCCESS:
                        mIsCreateHandleSuccess = true;
                        break;
                    case LFLivenessSDK.LF_LIVENESS_INIT_FAIL_LICENSE_OUT_OF_DATE://-15
                        mIsCreateHandleSuccess = false;
                        errorCode = LivenessActivity.RESULT_SDK_INIT_FAIL_LICENSE_OUT_OF_DATE;//6
                        break;
                    case LFLivenessSDK.LF_LIVENESS_INIT_FAIL_BIND_APPLICATION_ID: //-14
                        mIsCreateHandleSuccess = false;
                        errorCode = LivenessActivity.RESULT_SDK_INIT_FAIL_APPLICATION_ID_ERROR;
                        break;
                    case LFLivenessSDK.LF_LIVENESS_INIT_FAIL_MODEL_OUT_OF_DATA://-9
                        mIsCreateHandleSuccess = false;
                        errorCode = LivenessActivity.RESULT_SDK_INIT_FAIL_OUT_OF_DATE;
                        break;
                    case LFLivenessSDK.LF_LIVENESS_INIT_FAIL_MODEL_NO_LIC_PATH://-100
                        mIsCreateHandleSuccess = false;
                        errorCode = LivenessActivity.RESULT_FAILED_NO_LIC_PATH;
                        break;
                    default:
                        mIsCreateHandleSuccess = false;
                        errorCode = LivenessActivity.RESULT_INTERNAL_ERROR;
                        break;
                }
                if (mIsCreateHandleSuccess) {
                    mIsDetectorStartSuccess = mDetector.start(getLivenessConfig());
                } else {
                    onErrorHappen(errorCode);
                }
            } catch (Throwable e) {
                onErrorHappen(LivenessActivity.RESULT_INTERNAL_ERROR);
            }
        }
    }

    private void stopDetectThread() {
        mIsKilled = true;
        mIsCreateHandleSuccess = false;
        mDetectorExecutor.shutdown();
        try {
            mDetectorExecutor.awaitTermination(100, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mDetectorExecutor = null;
    }

    void restartDetect(boolean bRestartTime) {
        if (null == mListener) {
            return;
        }
        if (bRestartTime) {
            mListener.onLivenessDetect(mMotionList[mCurrentMotion].getValue(), mCurrentMotion, null, null, null);
        }
    }

    public void resetStatus(boolean fAlert) {
        boolean bRestartTime = fAlert;
        if (mCurrentMotion > 0) {
            bRestartTime = true;
        }
        resetLivenessResult();
        mCurrentMotion = 0;
        restartDetect(bRestartTime);
    }

    private void resetLivenessResult() {
        int count = mLiveResult.length;
        for (int i = 0; i < count; i++) {
            mLiveResult[i] = false;
        }
    }

    public void registerLivenessDetectCallback(OnLivenessCallBack callback) {
        mListener = callback;
    }

    public void onTimeEnd() {
        finishDetect(Constants.LIVENESS_TIME_OUT, mCurrentMotion);
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (DEBUG_PREVIEW) {
            debugFps();
        }
        if (mIsFirstPreviewFrame) {
            mFirstFrameTime = System.currentTimeMillis();
            mIsFirstPreviewFrame = false;
        }
        long intervalTime = System.currentTimeMillis() - mFirstFrameTime;
        if (intervalTime <= DETECT_WAIT_TIME) {
            if (mBeginShowWaitUIBoolean && mListener != null) {
                mListener.onLivenessDetect(Constants.DETECT_BEGIN_WAIT,
                        1, null, null, null);
                mBeginShowWaitUIBoolean = false;
            }
            addPreviewCallbackBuffer();
        } else {
            if (!mEndShowWaitUIBoolean && mListener != null) {
                mListener
                        .onLivenessDetect(Constants.DETECT_END_WAIT, 1, null, null, null);
                mEndShowWaitUIBoolean = true;
                startLiveness();
            }
            if (!mPaused && !mNV21DataIsReady) {
                synchronized (mNv21) {
                    if (data != null && mNv21 != null
                            && mNv21.length >= data.length) {
                        System.arraycopy(data, 0, mNv21, 0, data.length);
                        mNV21DataIsReady = true;
                    }
                }
            }
        }
    }

    public interface OnLivenessCallBack {
        void onLivenessDetect(int value, int status, byte[] livenessEncryptResult,
                              byte[] videoResult, LFLivenessSDK.LFLivenessImageResult[] imageResult);
    }

    public void stopLiveness() {
        mPaused = true;
    }

    public void startLiveness() {
        resetStatus(false);
        mPaused = false;
    }

    public void addSequentialInfo(int type, float[] values) {
        if (!mPaused && mDetector != null
                && mIsCreateHandleSuccess) {
            StringBuilder sb = new StringBuilder();
            sb.append(values[0])
                    .append(" ")
                    .append(values[1])
                    .append(" ")
                    .append(values[2])
                    .append(" ");
            LFLivenessSDK.LFWrapperSequentialInfo sequentialInfo = null;
            switch (type) {
                case Sensor.TYPE_MAGNETIC_FIELD:
                    sequentialInfo = LFLivenessSDK.LFWrapperSequentialInfo.MAGNETIC_FIELD;
                    break;
                case Sensor.TYPE_ACCELEROMETER:
                    sequentialInfo = LFLivenessSDK.LFWrapperSequentialInfo.ACCLERATION;
                    break;
                case Sensor.TYPE_ROTATION_VECTOR:
                    sequentialInfo = LFLivenessSDK.LFWrapperSequentialInfo.ROTATION_RATE;
                    break;
                case Sensor.TYPE_GRAVITY:
                    sequentialInfo = LFLivenessSDK.LFWrapperSequentialInfo.GRAVITY;
                    break;
            }
            try {
                if (sequentialInfo != null) {
                    mDetector
                            .addSequentialInfo(sequentialInfo
                                    .getValue(), sb.toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            sb = null;
        }
    }

    private void debugFps() {
        if (mFrameCount == 0) {
            mStartTime = System.currentTimeMillis();
        }
        mFrameCount++;
        long testTime = System.currentTimeMillis() - mStartTime;
        if (testTime > 1000) {
            Log.i(TAG, "onPreviewFrame FPS = " + mFrameCount);
            Toast.makeText(getActivity(), "FPS: " + mFrameCount, Toast.LENGTH_SHORT).show();
            mFrameCount = 0;
        }
    }
}
